/**
 * Copyright © 2019 同程艺龙 (zhihui.li@ly.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ly.train.flower.common.util;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import com.ly.train.flower.common.logging.Logger;
import com.ly.train.flower.common.logging.LoggerFactory;

public abstract class ServiceThread implements Runnable {
  private static final Logger log = LoggerFactory.getLogger(ServiceThread.class);

  private static final long JOIN_TIME = 90 * 1000;
  private static final AtomicInteger index = new AtomicInteger();

  protected final Thread thread;
  protected final CountDownLatch2 waitPoint = new CountDownLatch2(1);
  protected volatile AtomicBoolean hasNotified = new AtomicBoolean(false);
  protected volatile boolean stopped = false;

  public ServiceThread() {
    this.thread = new Thread(this, this.getServiceName() + "-" + index.incrementAndGet());
  }

  public abstract String getServiceName();

  public void start() {
    this.thread.start();
  }


  @Override
  public final void run() {
    try {
      doService();
    } catch (Exception e) {
      log.error("", e);
    }
  }

  public abstract void doService();

  public void shutdown() {
    this.shutdown(false);
  }

  public void shutdown(final boolean interrupt) {
    this.stopped = true;
    log.info("shutdown thread " + this.getServiceName() + " interrupt " + interrupt);

    if (hasNotified.compareAndSet(false, true)) {
      waitPoint.countDown(); // notify
    }

    try {
      if (interrupt) {
        this.thread.interrupt();
      }

      long beginTime = System.currentTimeMillis();
      if (!this.thread.isDaemon()) {
        this.thread.join(this.getJointime());
      }
      long eclipseTime = System.currentTimeMillis() - beginTime;
      log.info("join thread " + this.getServiceName() + " eclipse time(ms) " + eclipseTime + " " + this.getJointime());
    } catch (InterruptedException e) {
      log.error("Interrupted", e);
    }
  }

  public long getJointime() {
    return JOIN_TIME;
  }

  public void stop() {
    this.stop(false);
  }

  public void stop(final boolean interrupt) {
    this.stopped = true;
    log.info("stop thread " + this.getServiceName() + " interrupt " + interrupt);

    if (hasNotified.compareAndSet(false, true)) {
      waitPoint.countDown(); // notify
    }

    if (interrupt) {
      this.thread.interrupt();
    }
  }

  public void makeStop() {
    this.stopped = true;
    log.info("makestop thread " + this.getServiceName());
  }

  public void wakeup() {
    if (hasNotified.compareAndSet(false, true)) {
      waitPoint.countDown(); // notify
    }
  }

  protected void waitForRunning(long interval) {
    if (hasNotified.compareAndSet(true, false)) {
      this.onWaitEnd();
      return;
    }

    // entry to wait
    waitPoint.reset();

    try {
      waitPoint.await(interval, TimeUnit.MILLISECONDS);
    } catch (InterruptedException e) {
      log.error("Interrupted", e);
    } finally {
      hasNotified.set(false);
      this.onWaitEnd();
    }
  }

  protected void onWaitEnd() {}

  public boolean isStopped() {
    return stopped;
  }
}
